﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using AbstractBugTracker;
using Engine.Buggets;
using SmartAssembly;
using Utilities;

namespace Engine
{
    internal sealed class ReportProcessingPipeline<TBug, TBugDef> : Container
        where TBug : class, IBug
        where TBugDef : BugDef
    {
        private readonly IBugPresenter<TBug, TBugDef> m_BugPresenter;
        private readonly IBugTracker<TBug, TBugDef> m_BugTracker;
        private readonly IEmailSender m_EmailSender;
        private readonly NormalCreator<TBug, TBugDef> m_NormalCreator;
        private readonly FList<IBugget<TBug>> m_Buggets;

        protected override object GetService(Type service)
        {
            if (service == typeof(IBugPresenter<TBug, TBugDef>))
            {
                return m_BugPresenter;
            }
            if (service == typeof(IBugTracker<TBug, TBugDef>))
            {
                return m_BugTracker;
            }
            if (service == typeof(IEmailSender))
            {
                return m_EmailSender;
            }
            return base.GetService(service);
        }

        internal ReportProcessingPipeline(IBugPresenter<TBug, TBugDef> bugPresenter,
                                          IBugTracker<TBug, TBugDef> bugTracker,
                                          IEmailSender emailSender,
                                          IDictionary<string, string> emailTemplates,
                                          IEnumerable<IPluginComponent<TBug, TBugDef>> plugins)
        {
            m_BugPresenter = bugPresenter;
            m_BugTracker = bugTracker;
            m_EmailSender = emailSender;
            foreach (var plugin in (plugins ?? Enumerable.Empty<IPluginComponent<TBug, TBugDef>>()))
            {
                Add(plugin);
            }

            m_NormalCreator = new NormalCreator<TBug, TBugDef>(bugPresenter, bugTracker);
            SecondaryCreator<TBug, TBugDef> secondaryCreator = new SecondaryCreator<TBug, TBugDef>(bugPresenter, bugTracker);
            PropertyFiltersFilterer<TBug, TBugDef> propertyFiltersFilterer = new PropertyFiltersFilterer<TBug, TBugDef>(bugTracker);
            Reopener<TBug> reopener = new Reopener<TBug>();
            Commenter<TBug, TBugDef> commenter = new Commenter<TBug, TBugDef>(bugPresenter);
            BuildRaisedOn<TBug> buildRaisedOn = new BuildRaisedOn<TBug>();
            IncrementReportCount<TBug> incrementReportCount = new IncrementReportCount<TBug>();
            Spammer<TBug> spammer = new Spammer<TBug>(emailSender, emailTemplates);
            IEnumerable<IBugget<TBug>> pluginBuggets = Components.OfType<IPluginComponent<TBug, TBugDef>>().Select(c => c.GetBugget());

            m_Buggets = FList<IBugget<TBug>>.Empty;
            m_Buggets = m_Buggets.Cons(secondaryCreator);
            m_Buggets = m_Buggets.Cons(propertyFiltersFilterer);//TODO SARGE-241: don't want to ignore bugs we've just opened
            m_Buggets = m_Buggets.Cons(reopener);
            m_Buggets = m_Buggets.Cons(commenter);
            m_Buggets = m_Buggets.Cons(buildRaisedOn);
            m_Buggets = m_Buggets.Cons(incrementReportCount);
            m_Buggets = m_Buggets.Cons(spammer);
            m_Buggets = pluginBuggets.Aggregate(m_Buggets, (current, pluginBugget) => current.Cons(pluginBugget));

            m_Buggets = m_Buggets.Rev();
        }

        internal string ProcessReport(Report report)
        {
            ProcessedReport processedReport = new ProcessedReport(report);
            IEnumerable<Tuple<ProcessedReport, TBug>> reportPairs = m_NormalCreator.Run(processedReport);
            RunTheRest(reportPairs, m_Buggets);

            var key = GetBugTrackerKey(reportPairs);
            return key;
        }

        private static string GetBugTrackerKey(IEnumerable<Tuple<ProcessedReport, TBug>> reportPairs)
        {
            var keys = reportPairs.Select(rp => rp.Two.Key);
            var key = String.Join(",", keys.ToArray());
            return key;
        }

        private static void RunTheRest(IEnumerable<Tuple<ProcessedReport, TBug>> reportPairs, FList<IBugget<TBug>> buggets)
        {
            foreach (Tuple<ProcessedReport, TBug> eachReportPair in reportPairs)
            {
                if (!buggets.IsEmpty) // this must be inside the foreach loop, otherwise the lazy evaluation never runs
                {
                    var updatedReportPairs = buggets.Head.Run(eachReportPair.One, eachReportPair.Two);
                    RunTheRest(updatedReportPairs, buggets.Tail);
                }
                else
                {
                    // Now all the buggets are run, commit any changes
                    eachReportPair.Two.CommitChanges();
                }
            }
        }
    }
}
